package com.smssalama.gui;

import java.util.Vector;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.CustomItem;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.ItemCommandListener;
import javax.microedition.lcdui.ItemStateListener;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.TextField;

import com.smssalama.AlertManager;
import com.smssalama.SMSSalama;
import com.smssalama.SMSSalamaSession;
import com.smssalama.Utils;
import com.smssalama.i18n.CommandLabels;
import com.smssalama.i18n.ErrorMessages;
import com.smssalama.i18n.Labels;
import com.smssalama.storage.contact.Contact;
import com.smssalama.storage.framework.ObjectStore;
import com.smssalama.storage.framework.StorableComparatorImpl;
import com.smssalama.storage.framework.ObjectStore.ObjectEnumeration;

/**
 * <p>Provides means for a user to select a contact from the contact list.</p>
 * <p>If a user enters a number, the number as </p> 
 * @author Arnold P. Minde
 *
 */
public class SelectContact extends Form {
	private Labels LABELS = Labels.labels();
	private CommandLabels CMD_LABELS = CommandLabels.cmdLabels();
	
	/**
	 * Listens for Contact Item selection
	 * 
	 * @author Arnold P. Minde
	 *
	 */
	private final class ContactItemCommandListener implements
			ItemCommandListener {
		public void commandAction(Command command, Item item) {
			if (command == SelectContact.this.selectItemCmd) {
				try {
					setSelectedNumber(((ContactItem) item).phoneNumber);
				} catch (Exception e) {
					AlertManager.showError(item, e);
				}
			}
		}
	}

	private class ContactItem extends CustomItem {
		private String phoneNumber;

		public ContactItem(String name, String number) {
			super(name);
			this.phoneNumber = number;
			// this.setPreferredSize(127, this.getPreferredHeight());
		}

		public String getPhoneNumber() {
			return this.phoneNumber;
		}

		public void setPhoneNumber(String phoneNumber) {
			this.phoneNumber = phoneNumber;
			this.invalidate();
		}

		public void setName(String name) {
			this.setLabel(name);
			this.invalidate();
		}

		private Font getFont() {
			return Font.getFont(Font.FONT_STATIC_TEXT);
		}

		protected int getMinContentWidth() {
			// return getDisplayable().getWidth();
			return Math.min(getFont().stringWidth(this.getLabel()), getFont()
					.stringWidth(this.phoneNumber));
		}

		protected int getMinContentHeight() {
			// return getDisplayable().getHeight();
			return getFont().getHeight();
		}

		protected int getPrefContentWidth(int height) {
			// return getDisplayable().getWidth();
			return Math.max(getFont().stringWidth(this.getLabel()), getFont()
					.stringWidth(this.phoneNumber));
		}

		protected int getPrefContentHeight(int width) {
			// return getDisplayable().getHeight();
			return getFont().getHeight();
		}

		protected void paint(Graphics g, int w, int h) {
			g.drawString(this.phoneNumber, 0, 0, 0);
		}
	}

	abstract class SearchRequest{
		String searchString;
		boolean terminated = false;
		abstract void completed(Vector contacts);
	}

	private CommandListener commandListener = null;
	private final ContactItemCommandListener contactItemCommandListener = new ContactItemCommandListener();
	
	final TextField searchField = new TextField(LABELS.enterNumberOrName(), "", 100, TextField.ANY);
	final Vector contacts = new Vector();
	Command selectItemCmd = new Command(CMD_LABELS.select(),
			Command.ITEM, 1);
	Command newContactCmd = new Command(CMD_LABELS.newContact(),
			Command.ITEM, 1);
	Command useNumberCmd = new Command(CMD_LABELS.ok(),
			Command.ITEM, 1);
	private String selectedNumber;

	private final int staticFieldsCount;

	public SelectContact(String title) {
		super(title);
		//this.searchField.setString("+5550000");
		this.setItemStateListener(new ItemStateListener() {
			public void itemStateChanged(Item item) {
				updateSearchFieldCmds();
				newSearch();
			}
		});
		// searchField.notifyStateChanged();
		this.append(this.searchField);
		this.searchField.setItemCommandListener(new ItemCommandListener() {
			public void commandAction(Command command, Item item) {
				if (command == SelectContact.this.useNumberCmd) {
					try {
						setSelectedNumber(SelectContact.this.searchField
								.getString());
					} catch (Exception e) {
						e.printStackTrace();
						AlertManager.showError(item, e);
					}
				} else if (command == SelectContact.this.newContactCmd) {
					//launch a dialog to add a new contact and return its
					final ContactEditor editor = new ContactEditor(SelectContact.this);
					if(Utils.isValidaPhoneNumber(searchField.getString())){
						editor.phoneNumberField.setString(searchField.getString());
					}else{
						editor.nameField.setString(searchField.getString());
					}					
					//Override its command listener so that we can monitor when a new contact is saved
					editor.setCommandListener(new CommandListener(){
						public void commandAction(Command command, Displayable displayable) {
							editor.commandAction(command, displayable);
							if(editor.getContact()!=null && editor.getContact().getRecordId()!=0){
								String number = editor.getContact().getPhoneNumber();
								searchField.setString(number);
								if(allContactsCache.size()>0){
									//If it is loaded, we need to add the new contact to it so as it can be seen
									allContactsCache.addElement(editor.getContact());
								}
								//initiate a new search
								newSearch();
							}
						}});
					AlertManager.showDisplayable(editor);
					
					// number
				}
			}
		});
		this.append(new Spacer(10, 10));
		this.staticFieldsCount = 2;
	}

	/**
	 * Retrieves the number of contacts on display after search
	 * 
	 * @return
	 */
	public int getCountactsCount() {
		return this.size() - this.staticFieldsCount;
	}
	
	private SearchRequest currentRequest = null;
	private Thread searchThread;

	private synchronized void newSearch(){
		if(currentRequest!=null){
			currentRequest.terminated = true;
		}
		currentRequest = new SearchRequest(){
			SearchRequest request = this;
			void completed(final Vector contacts) {
				if(!terminated){
					Runnable r = new Runnable(){
						public void run() {
							updateScreen(request, contacts);							
						}};
					Display.getDisplay(SMSSalama.getInstance()).callSerially(r);										
				}
			}};
		currentRequest.searchString = searchField.getString();
		Runnable r = new Runnable(){
			final SearchRequest threadRequest = currentRequest;
			public synchronized void run() {
				try {
					this.wait(500);
					if(!threadRequest.terminated){
						Runnable r = new Runnable(){
							public void run() {
								try {
									selectContacts(threadRequest);
								} catch (Exception e) {									
									e.printStackTrace();
									throw new RuntimeException(e.getMessage());
								}
							}};
							Display.getDisplay(SMSSalama.getInstance()).callSerially(r);		
					}					
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException(e.getMessage());
				}
			}};
		new Thread(r).start();
	}
	
	private Vector allContactsCache = new Vector();
	
	private void selectContacts(final SearchRequest request) throws Exception {
		String searchString = request.searchString != null ? request.searchString
				: "";
		searchString = searchString.toLowerCase();
		if (searchString == null || searchString.length() <= 0
				|| searchString.equals("+")) {
			return;
		}
		Vector result = new Vector();
		if (allContactsCache.size() == 0) {
			// Search ObjectStore
			ObjectStore store = SMSSalamaSession.getSession().getUserSettings()
					.getContactsStore();
			ObjectEnumeration contactsEnumeration = store.search(null, null);

			while (contactsEnumeration.hasNextObject() && !request.terminated) {
				final Contact contact = (Contact) contactsEnumeration
						.nextObject();
				allContactsCache.addElement(contact);
				applyFilter(searchString, contact, result);
			}
		} else {
			// Search local list
			for(int i=0; i<allContactsCache.size() && !request.terminated;i++){
				Contact contact = (Contact)allContactsCache.elementAt(i);
				applyFilter(searchString, contact, result);
			}
		}
		// Sort the results
		StorableComparatorImpl comparator = new StorableComparatorImpl(
				new Object[] { Contact.COMPARE_CONTACT_NAME }) {
			public boolean terminate() {
				return request.terminated;
			}
		};
		ObjectStore.quickSort(result, comparator);
		request.completed(result);
	}
	
	private void applyFilter(String searchString, Contact contact, Vector result){
		String name = contact.getContactName() != null ? contact
				.getContactName() : "";
		name = name.toLowerCase();
		String number = contact.getPhoneNumber() != null ? contact
				.getPhoneNumber() : "";
		if (!(name.indexOf(searchString) >= 0) && !(number.indexOf(searchString) >= 0)) {
			return;
		}
		result.addElement(contact);		
	}

	private synchronized void updateScreen(SearchRequest request, Vector newContacts){
		for(int i=0; i<newContacts.size() && !request.terminated;i++){
			Contact contact = (Contact)newContacts.elementAt(i);
			ContactItem contactItem = null;
			final String displayName = contact.getContactName();
			if (i < SelectContact.this.contacts.size()) {
				SelectContact.this.contacts.setElementAt(contact, i);
				contactItem = (ContactItem) SelectContact.this.get(i + 2);
				contactItem.setName(displayName);
				contactItem.setPhoneNumber(contact.getPhoneNumber());
			} else {
				SelectContact.this.contacts.addElement(contact);
				contactItem = new ContactItem(displayName, contact
						.getPhoneNumber());
				contactItem.setPreferredSize(Integer.MAX_VALUE, -1);
				contactItem.setDefaultCommand(SelectContact.this.selectItemCmd);
				SelectContact.this.append(contactItem);
			}				
			contactItem.setItemCommandListener(contactItemCommandListener);
			if (SelectContact.this.contacts.size() == 0
					&& Utils.isValidaPhoneNumber(SelectContact.this.searchField.getString())) {
				SelectContact.this.searchField
						.setDefaultCommand(SelectContact.this.useNumberCmd);
			} else {
				SelectContact.this.searchField
						.removeCommand(SelectContact.this.useNumberCmd);
			}			
		}
		if(!request.terminated){
			i = newContacts.size();
			while (this.contacts.size() > i) {
				this.contacts.removeElementAt(this.contacts.size() - 1);
			}
			while (this.size() > i + 2) {
				this.delete(this.size() - 1);
			}
		}
		updateSearchFieldCmds();
	}
	
	private void updateSearchFieldCmds(){
		if(this.size()<=2 && Utils.isValidaPhoneNumber(searchField.getString())){
			searchField.addCommand(SelectContact.this.newContactCmd);
			searchField.addCommand(SelectContact.this.useNumberCmd);
			searchField.setDefaultCommand(SelectContact.this.useNumberCmd);
		}else{
			searchField.removeCommand(SelectContact.this.newContactCmd);
			searchField.removeCommand(SelectContact.this.useNumberCmd);
		}
	}
	int updateRound = 0;
	int i = 0;
	int lastIndex = 0;
	/**
	 * 
	 * @deprecated
	 */
	public void updateContactList() throws Exception {
		final int thisUpdateround = ++updateRound;
		ObjectStore store = SMSSalamaSession.getSession().getUserSettings()
				.getContactsStore();
		ObjectEnumeration enumeration = store.search(null,
				new StorableComparatorImpl(
						new Object[] { Contact.COMPARE_CONTACT_NAME }));
		String s = this.searchField.getString() != null ? this.searchField
				.getString() : "";
		s = s.toLowerCase();
		i = 0;
		lastIndex = -1;
		if (s != null && s.length() > 0 && !s.equals("+")) {
			while (enumeration.hasNextObject() && thisUpdateround==updateRound) {
				final Contact contact = (Contact) enumeration.nextObject();
				String name = contact.getContactName() != null ? contact
						.getContactName() : "";
				name = name.toLowerCase();
				String number = contact.getPhoneNumber() != null ? contact
						.getPhoneNumber() : "";
				if (!(name.indexOf(s) >= 0) && !(number.indexOf(s) >= 0)) {
					continue;
				}
				final String displayName = contact.getContactName();
				Runnable r = new Runnable(){
					int j = i;
					boolean isLast = lastIndex == j;
					public void run() {
						if(thisUpdateround!=updateRound) return;
						ContactItem contactItem = null;
						if (j < SelectContact.this.contacts.size()) {
							SelectContact.this.contacts.setElementAt(contact, j);
							contactItem = (ContactItem) SelectContact.this.get(j + 2);
							contactItem.setName(displayName);
							contactItem.setPhoneNumber(contact.getPhoneNumber());
						} else {
							SelectContact.this.contacts.addElement(contact);
							/*
							 * StringItem contactItem = new StringItem(displayName,
							 * contact.getPhoneNumber());
							 * contactItem.setPreferredSize(Integer.MAX_VALUE, -1);
							 * contactItem.addCommand(new Command.);
							 */
							contactItem = new ContactItem(displayName, contact
									.getPhoneNumber());
							contactItem.setPreferredSize(Integer.MAX_VALUE, -1);
							contactItem.setDefaultCommand(SelectContact.this.selectItemCmd);
							SelectContact.this.append(contactItem);
						}				
						contactItem.setItemCommandListener(contactItemCommandListener);
						if (SelectContact.this.contacts.size() == 0
								&& Utils.isValidaPhoneNumber(SelectContact.this.searchField.getString())) {
							SelectContact.this.searchField
									.setDefaultCommand(SelectContact.this.useNumberCmd);
						} else {
							SelectContact.this.searchField
									.removeCommand(SelectContact.this.useNumberCmd);
						}
					}};
				Display.getDisplay(SMSSalama.getInstance()).callSerially(r);
				i++;
			}
			lastIndex=i;
		}
		while (this.contacts.size() > i && thisUpdateround==updateRound) {
			this.contacts.removeElementAt(this.contacts.size() - 1);
		}
		while (this.size() > i + 2 && thisUpdateround==updateRound) {
			this.delete(this.size() - 1);
		}
	}

	public void setCommandListener(CommandListener l) {
		super.setCommandListener(l);
		this.commandListener = l;
	}

	public String getSelectedNumber() {
		return this.selectedNumber;
	}

	protected void setSelectedNumber(String phoneNumber) throws Exception {
		phoneNumber = Utils.fixPhoneNumberPrefix(phoneNumber);
		if (!Utils.isValidaPhoneNumber(phoneNumber)) {
			throw new Exception(ErrorMessages.errors
					.invalidPhoneNumber(phoneNumber));
		} else {
			this.selectedNumber = phoneNumber;
			if (this.commandListener != null) {
				this.commandListener.commandAction(new Command("", Command.OK,
						1), SelectContact.this);
			}
		}
	}
}
